Crate objc2_app_kit

source ·
Expand description

§Bindings to the AppKit framework

See Apple’s docs and the general docs on framework crates for more information.

Note that a lot of functionality in AppKit requires that the application has initialized properly, which is only done after the application delegate has received applicationDidFinishLaunching.

You should aspire to do all your UI initialization work in there!

§NSWindow

Be careful when creating NSWindow if it’s not inside a window controller; in those cases you’re required to call window.releasedWhenClosed(false) to get correct memory management, which is also why the creation methods for NSWindow are unsafe.

§Examples

Implementing NSApplicationDelegate for a custom class.

#![deny(unsafe_op_in_unsafe_fn)]
use objc2::rc::Id;
use objc2::runtime::ProtocolObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{
    ns_string, MainThreadMarker, NSCopying, NSNotification, NSObject, NSObjectProtocol, NSString,
};

#[derive(Debug)]
#[allow(unused)]
struct Ivars {
    ivar: u8,
    another_ivar: bool,
    box_ivar: Box<i32>,
    maybe_box_ivar: Option<Box<i32>>,
    id_ivar: Id<NSString>,
    maybe_id_ivar: Option<Id<NSString>>,
}

declare_class!(
    struct AppDelegate;

    // SAFETY:
    // - The superclass NSObject does not have any subclassing requirements.
    // - Main thread only mutability is correct, since this is an application delegate.
    // - `AppDelegate` does not implement `Drop`.
    unsafe impl ClassType for AppDelegate {
        type Super = NSObject;
        type Mutability = mutability::MainThreadOnly;
        const NAME: &'static str = "MyAppDelegate";
    }

    impl DeclaredClass for AppDelegate {
        type Ivars = Ivars;
    }

    unsafe impl NSObjectProtocol for AppDelegate {}

    unsafe impl NSApplicationDelegate for AppDelegate {
        #[method(applicationDidFinishLaunching:)]
        fn did_finish_launching(&self, notification: &NSNotification) {
            println!("Did finish launching!");
            // Do something with the notification
            dbg!(notification);
        }

        #[method(applicationWillTerminate:)]
        fn will_terminate(&self, _notification: &NSNotification) {
            println!("Will terminate!");
        }
    }
);

impl AppDelegate {
    fn new(ivar: u8, another_ivar: bool, mtm: MainThreadMarker) -> Id<Self> {
        let this = mtm.alloc();
        let this = this.set_ivars(Ivars {
            ivar,
            another_ivar,
            box_ivar: Box::new(2),
            maybe_box_ivar: None,
            id_ivar: NSString::from_str("abc"),
            maybe_id_ivar: Some(ns_string!("def").copy()),
        });
        unsafe { msg_send_id![super(this), init] }
    }
}

fn main() {
    let mtm: MainThreadMarker = MainThreadMarker::new().unwrap();

    let app = NSApplication::sharedApplication(mtm);
    app.setActivationPolicy(NSApplicationActivationPolicy::Regular);

    // configure the application delegate
    let delegate = AppDelegate::new(42, true, mtm);
    let object = ProtocolObject::from_ref(&*delegate);
    app.setDelegate(Some(object));

    // run the app
    unsafe { app.run() };
}

An example showing basic and a bit more advanced usage of NSPasteboard.

//! Read from the global pasteboard, and write a new string into it.
//!
//! Works on macOS 10.7+
#![deny(unsafe_op_in_unsafe_fn)]

use objc2::rc::Id;
use objc2::runtime::{AnyClass, AnyObject, ProtocolObject};
use objc2::ClassType;
use objc2_app_kit::{NSPasteboard, NSPasteboardTypeString};
use objc2_foundation::{NSArray, NSCopying, NSString};

/// Simplest implementation
pub fn get_text_1(pasteboard: &NSPasteboard) -> Option<Id<NSString>> {
    unsafe { pasteboard.stringForType(NSPasteboardTypeString) }
}

/// More complex implementation using `readObjectsForClasses:options:`,
/// intended to show how some patterns might require more knowledge of
/// nitty-gritty details.
pub fn get_text_2(pasteboard: &NSPasteboard) -> Option<Id<NSString>> {
    // The NSPasteboard API is a bit weird, it requires you to pass classes as
    // objects, which `objc2_foundation::NSArray` was not really made for - so
    // we convert the class to an `AnyObject` type instead.
    //
    // TODO: Investigate and find a better way to express this in `objc2`.
    let string_class = {
        let cls: *const AnyClass = NSString::class();
        let cls = cls as *mut AnyObject;
        unsafe { Id::from_raw(cls).unwrap() }
    };
    let class_array = NSArray::from_vec(vec![string_class]);
    let objects = unsafe { pasteboard.readObjectsForClasses_options(&class_array, None) };

    let obj: *const AnyObject = objects?.first()?;
    // And this part is weird as well, since we now have to convert the object
    // into an NSString, which we know it to be since that's what we told
    // `readObjectsForClasses:options:`.
    let obj = obj as *mut NSString;
    Some(unsafe { Id::retain(obj) }.unwrap())
}

pub fn set_text(pasteboard: &NSPasteboard, text: &NSString) {
    let _ = unsafe { pasteboard.clearContents() };
    let obj = ProtocolObject::from_id(text.copy());
    let objects = NSArray::from_vec(vec![obj]);
    let res = unsafe { pasteboard.writeObjects(&objects) };
    if !res {
        panic!("Failed writing to pasteboard");
    }
}

fn main() {
    let pasteboard = unsafe { NSPasteboard::generalPasteboard() };
    let impl_1 = get_text_1(&pasteboard);
    let impl_2 = get_text_2(&pasteboard);
    println!("Pasteboard text from implementation 1 was: {impl_1:?}");
    println!("Pasteboard text from implementation 2 was: {impl_2:?}");
    assert_eq!(impl_1, impl_2);

    let s = NSString::from_str("Hello, world!");
    set_text(&pasteboard, &s);
    println!("Now the pasteboard text should be: {s:?}");
    assert_eq!(Some(s), get_text_1(&pasteboard));
}

Structs§

Constants§

Statics§

Traits§

Functions§

Type Aliases§